package com.dhcc.utils.string;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.management.RuntimeErrorException;

import com.dhcc.utils.Operator;
import com.dhcc.utils.OperatorException;

/**
 * 支持 eval 方法
 * @author hibean@qq.com 2017年8月2日
 *
 */
public class EvalOperator implements Operator<String> {
	private static final String START = System.getProperty("com.dhcc.utils.string.EvalOperator.START", "{");
	private static final String END = System.getProperty("com.dhcc.utils.string.EvalOperator.END", "}");
	private Map<String, WeakReference<List<Token>>> cache = new HashMap<String, WeakReference<List<Token>>>(); 
	private static final Object EMPTY = new Object();
	@Override
	public Object invoke(String operator, String first, Object[] args) {
		List<Token> tokens = getToken(first);
		StringBuilder result = new StringBuilder();
		for (Token token : tokens) {
			token.output(result, args[0]);
		}
		return result.toString();
	}
	
	private List<Token> getToken(String str) {
		WeakReference<List<Token>> wr;
		List<Token> tokens;
		if( (wr=cache.get(str))==null || (tokens = wr.get())==null){
			tokens = rebuild(str);
			wr = new WeakReference<List<EvalOperator.Token>>(tokens);
			cache.put(str, wr);
		}
		return tokens;
	}

	private List<Token> rebuild(String str) {
		List<Token> tokens = new LinkedList<EvalOperator.Token>();
		int pos = 0;
		while(true){
			int index = str.indexOf(START, pos);
			if(index>pos){
				tokens.add(new TextToken(str.substring(pos, index)));
				pos = index + START.length();
				index = str.indexOf(END, pos);
				if(index>pos){
					tokens.add(new EvalToken(str.substring(pos, index)));
					pos = index + END.length();
				}else{
					throw new OperatorException("eval 语法错误，有开头没有结尾");
				}
			}else{
				tokens.add(new TextToken(str.substring(pos)));
				return tokens;
			}
		}
	}

	private static interface Token{
		void output(StringBuilder builder, Object target);
	}
	private static class TextToken implements Token {
		private String text;
		public TextToken(String text) {
			this.text = text;
		}
		@Override
		public void output(StringBuilder builder, Object target) {
			builder.append(text);
		}
		
	}
	private static class EvalToken implements Token {
		private String field;
		private MessageFormat fmt;
		private Evaluator evaluator;
		
		public EvalToken(String holder) {
			int index = holder.indexOf(",");
			this.field = index>0?holder.substring(0, index):holder;
			if(index>0){
				String fmtStr = holder.substring(index+1);
				fmt = new MessageFormat("{0,"+fmtStr+"}");
			}
		}
		@Override
		public void output(StringBuilder builder, Object target) {
			Object obj = eval(target, field);
			if(obj==EMPTY){
				obj = System.getProperty(field);
			}
			builder.append(fmt != null? fmt.format(new Object[]{obj}):obj);
		}
		private Object eval(Object target, String field2) {
			if(evaluator == null){
				evaluator = buildEvaluator(target, field2);
			}
			return evaluator.eval(target);
		}
		private Evaluator buildEvaluator(Object target, String field2) {
			if(target instanceof Map){
				return new MapEvaluator(field2);
			}else{
				return new PojoEvaluator(target.getClass(), field2);
			}
		}
		
	}
	private static interface Evaluator{
		Object eval(Object target);
	}
	
	private static class MapEvaluator implements Evaluator{
		private String key;
		public MapEvaluator(String key) {
			this.key = key;
		}
		@Override
		public Object eval(Object target) {
			if(target instanceof Map && ((Map) target).containsKey(key)){
				return ((Map)target).get(key);
			}else{
				return EMPTY;
			}
		}
		
	}
	private static class PojoEvaluator implements Evaluator{
		private Method getMethod;
		public PojoEvaluator(Class<?> clz, String field) {
			PropertyDescriptor pd;
			try {
				char[] chars = field.toCharArray();
				chars[0] = Character.toUpperCase(chars[0]);
				String cap = new String(chars);
				pd = new PropertyDescriptor(field, clz, "is"+cap, null);
				getMethod = pd.getReadMethod();
			} catch (IntrospectionException e) {
				
			}
		}
		@Override
		public Object eval(Object target) {
			if(getMethod==null){
				return EMPTY;
			}
			try {
				return getMethod.invoke(target);
			} catch (Exception e) {
				return EMPTY;
			}
		}
		
	}
}
